package com.spukmk3me.scenepack.pack0;

import java.util.Hashtable;

/* $if SPUKMK3ME_DEBUG$ */
import com.spukmk3me.debug.Logger;
/* $endif$ */

import com.spukmk3me.math.Matrix4f;
import com.spukmk3me.math.Rectangle;
import com.spukmk3me.math.Vector3f;
import com.spukmk3me.scene.Node;
import com.spukmk3me.scene.Scene;
import com.spukmk3me.util.DoublyLinkedList;
import com.spukmk3me.util.Util;
import com.spukmk3me.video.VideoAPI;

public class DefaultScene implements Scene
{
    public DefaultScene( VideoAPI vapi )
    {
        m_vapi          = vapi;
        m_managedNodes  = new Hashtable<String, DefaultNode>();
        
        DefaultNode rootFFNode = new DefaultNode();
        m_rootNode = createNode( ROOT );
        m_rootNode.c_next = m_rootNode.c_prev = rootFFNode;
        rootFFNode.c_next = rootFFNode.c_prev = m_rootNode;
        
        m_buffer    = new OrderedRenderBuffer( vapi );
    }

    @Override
    public VideoAPI getVideoAPI()
    {
    	return m_vapi;
    }
    
    @Override
    public DefaultNode createNode()
    {
        return createNode( null );
    }

    @Override
    public DefaultNode createNode( String name )
    {
        if ( name != null )
        {
            if ( !Util.isLegalName( name ) )
            {
                /* $if SPUKMK3ME_DEBUG$ */
                Logger.trace( "The name: \"" + name + "\" is illegal." );
                /* $endif$ */
                return null;
            }
            
            if ( m_managedNodes.containsKey( name ) )
            {
                /* $if SPUKMK3ME_DEBUG$ */
                Logger.trace( "Node with the name: \"" + name + "\" already exists." );
                /* $endif$ */
                return null;
            }
        }
        
        DefaultNode node = new DefaultNode( this, name, m_vapi.createMatrix() );
        
        if ( name != null )
            m_managedNodes.put( name, node );

        return node;
    }
    
    @Override
    public DefaultNode getRoot()
    {
        return m_rootNode;
    }
    
    @Override
    public DefaultNode getNode( String name )
    {
        return m_managedNodes.get( name );
    }
    
    @Override
    public void insertNodeAfter( Node node, Node predecessor )
    {
        ((DefaultNode)node).insertAfter( (DefaultNode)predecessor );
    }
    
    @Override
    public void attachNode( Node parent, Node child )
    {
        ((DefaultNode)parent).addChild( (DefaultNode)child );
    }
    
    @Override
    public void detachNode( Node node )
    {
        ((DefaultNode)node).detach();
    }
    
    @Override
    public Node detachNode( String name )
    {
        DefaultNode node = getNode( name );
        
        if ( node == null )
            return null;
        
        detachNode( node );
        return node;
    }
    
    @Override
    public void dropNode( Node node )
    {
        if ( node == null )
            throw new NullPointerException();
        
        DoublyLinkedList< Node > dropNodeList = new DoublyLinkedList< Node >();
        DoublyLinkedList< Node >.Iterator itr, etr;
        DefaultNode temp;
        int firstIndex = 0, nParseThisTurn;
        
        dropNodeList.push_back( node );
        
        while ( firstIndex != dropNodeList.length() )
        {
            itr = dropNodeList.first();
            
            for ( int i = firstIndex; i != 0; --i )
                itr.next();
            
            nParseThisTurn = dropNodeList.length() - firstIndex;
            firstIndex  = dropNodeList.length();
            
            for ( int i = nParseThisTurn; i != 0; --i )
            {
                temp = (DefaultNode)itr.data();
                
                if ( temp.c_children != null )
                {
                    DefaultNode child = temp.c_children.c_next;
                    
                    while ( child != temp.c_children )
                    {
                        dropNodeList.push_back( child );
                        child = child.c_next;
                    }
                }
                
                itr.next();
            }
        }
        
        itr = dropNodeList.last();
        etr = dropNodeList.end();
        
        //System.out.println( "Going to drop " + dropNodeList.length() + "nodes" );
        
        for ( ; !itr.equals( etr ); itr.back() )
        {
            temp = (DefaultNode)itr.data();
            temp.detach();
            temp.setEntity( null );
            temp.removeAllAnimators();
            
            if ( temp.getName() != null )
                m_managedNodes.remove( temp.getName() );
        }
    }
    
    @Override
    public Node dropNode( String name )
    {
        DefaultNode node = getNode( name );
        
        if ( node == null )
            return null;

        dropNode( node );
        return node;
    }
    
    @Override
    public void dropChildren( Node node )
    {
        DefaultNode dNode = (DefaultNode)node;
        
        if ( dNode.c_children == null )
            return;
        
        DefaultNode itr = dNode.c_children.c_next;
        
        while ( dNode.c_children != null )
        {
            itr = itr.c_next;
            dropNode( itr.c_prev );
        }
    }
    
    @Override
    public Node dropChildren( String name )
    {
        Node node = getNode( name );
        
        if ( node == null )
            return null;

        dropChildren( node );
        return node;
    }
    
    /**
     *  Render all scene and apply animator's animation.
     *  \details Nodes are rendered in parent-first order. Animator's works
     *  are applied after the node is rendered.\n
     *  Override this implementation if you find it not sufficient.
     *  @param deltaTime Time in seconds (16.16).
     */
    @Override
    public void render( int deltaTime )
    {
        scan( getRoot(), deltaTime );
        m_buffer.render();
    }
    
    @Override
    public void render( Node node, int deltaTime )
    {
        scan( (DefaultNode)node, deltaTime );
        m_buffer.render();
    }
    
    private void scan( DefaultNode top, int deltaTime )
    {
    	DefaultNode node, topNeedUpdate = null;
    	int order = 0;
    	
    	node = top;
    	order = 0;
    	
    	do
    	{
    		node.runAnimators( deltaTime );
    		
    		if ( node.needUpdate() || (topNeedUpdate != null) )
    		{
    			calculateAccumulatedData( node, order );
    			order = node.getAccumulatedAttrib().order;
    			
    			if ( topNeedUpdate == null )
    				topNeedUpdate = node;
    		}
    		
		    if ( node.getVisible() && node.getEnable() )
		        m_buffer.add( node );
    		
    		if ( node.getEnable() && (node.c_children != null) )
    		    node = node.c_children.c_next;
    		else
    		{
    			node = node.c_next;
    			
    			if ( node != top.c_next )
    			{
    				while ( node == node.c_parent.c_children )
    				{
	    				node = node.c_parent.c_next;
	    				
	    				if ( topNeedUpdate != null )
	    				{
	    					if ( node.c_prev == topNeedUpdate )
	    						topNeedUpdate = null;
	    				}
	    				
	    				if ( (node == top.c_next) )
	    				    break;
    				}
    			}
    		}
    	} while ( node != top.c_next );
    }
    
    private void calculateAccumulatedData( DefaultNode node, int order )
    {
    	AccumulatedAttribute a = node.getAccumulatedAttrib();

    	if ( node == m_rootNode )
    	{
    		a.acolor 	= node.getAdditionalColor();
    		a.pcolor 	= node.getPlaneColor();
    		a.order		= order + 1;
    		a.animate	= node.getAnimating();
    		node.calculateAccumulatedMatrix( a.matrix );
    		node.getClip( a.clip );
    	}
    	else
    	{
    		AccumulatedAttribute b = node.c_parent.getAccumulatedAttrib();
    		
    		a.acolor	= Util.addColor( b.acolor, node.getAdditionalColor() );
    		a.pcolor	= Util.mulColor( b.pcolor, node.getPlaneColor() );
    		a.order		= (node.getRenderOrder() < 0)? b.order + 1 : node.getRenderOrder();
    		a.animate	= b.animate && node.getAnimating();
    		
    		node.calculateAccumulatedMatrix( a.matrix );
    		a.matrix.mul_inv( b.matrix );

    		if ( node.getClippingUsage() )
    		{
    			node.getClip( a.clip );
    			findNextClipping( b.clip, a.clip, a.matrix );
    		}
    		else
    			a.clip.copy( b.clip );
    	}
    }
    
    private void findNextClipping( Rectangle currentClip, Rectangle untransformedNextClip, Matrix4f trans )
    {
        Vector3f v1 = new Vector3f( untransformedNextClip.x, untransformedNextClip.y, 0f );
        Vector3f v2 = new Vector3f( untransformedNextClip.x + untransformedNextClip.w,
            untransformedNextClip.y + untransformedNextClip.h, 0f );
        
        v1.mul( trans );
        v2.mul( trans );
        
        if ( v1.x > v2.x )
        {
            float temp = v1.x;
            
            v1.x = v2.x;
            v2.x = temp;
        }
        
        if ( v1.y > v2.y )
        {
            float temp = v1.y;
            
            v1.y = v2.y;
            v2.y = temp;
        }
        
        v1.x = Math.max( v1.x, currentClip.x );
        v1.y = Math.max( v1.y, currentClip.y );
        v2.x = Math.min( v2.x, currentClip.x + currentClip.w );
        v2.y = Math.min( v2.y, currentClip.y + currentClip.h );
        
        untransformedNextClip.x = (int)v1.x;
        untransformedNextClip.y = (int)v1.y;
        untransformedNextClip.w = (int)(v2.x - v1.x) + 1;
        untransformedNextClip.h = (int)(v2.y - v1.y) + 1;
    }
    
    private DefaultNode m_rootNode;
    private Hashtable<String, DefaultNode> m_managedNodes;
    
    private VideoAPI m_vapi;
    
    private OrderedRenderBuffer m_buffer;
}
